Imports System.Text.RegularExpressions

Namespace DataStream
    Public Class StreamCalculatorObject

#Region "Private Vairbales"
        Private mTargetName As String = ""
        Private mCalculator As Calculator
#End Region

#Region "Public Functions / Prototypes"
        ''' <summary>
        ''' Creates a new tree for calculating values
        ''' </summary>
        ''' <param name="EvalString"></param>
        ''' <remarks></remarks>
        Sub New(ByVal EvalString As String)
            Dim nIndex As Integer = EvalString.IndexOf("=")

            If nIndex <= 0 Then
                Throw New StreamCalculatorObjectParseException(EvalString, 0, "No Target Name")
            End If

            If nIndex = EvalString.Trim.Length - 1 Then
                Throw New StreamCalculatorObjectParseException(EvalString, EvalString.Trim.Length - 1, "No Evalulation Specified")
            End If

            Dim nOpenRound, nCloseRound, nOpenSquare, nCloseSquare, nOpenSquig, nCloseSquig As Integer
            For nCnt As Integer = 0 To EvalString.Length - 1
                Select Case EvalString.Chars(nCnt)
                    Case "["
                        nOpenSquare += 1
                    Case "]"
                        nCloseSquare += 1
                    Case "("
                        nOpenRound += 1
                    Case ")"
                        nCloseRound += 1
                    Case "}"
                        nCloseSquig += 1
                    Case "{"
                        nOpenSquig += 1
                End Select
            Next

            
            If nOpenSquare <> nCloseSquare Then
                If nOpenSquare > nCloseSquare Then
                    Throw New StreamCalculatorObjectParseException(EvalString, 0, "Square Brackets not closed, not enough ]'s")
                Else
                    Throw New StreamCalculatorObjectParseException(EvalString, 0, "Square Brackets not opened, too many ]'s")
                End If
            End If

            If nOpenRound <> nCloseRound Then
                If nOpenRound > nCloseRound Then
                    Throw New StreamCalculatorObjectParseException(EvalString, 0, "Round Brackets not closed, not enough )'s")
                Else
                    Throw New StreamCalculatorObjectParseException(EvalString, 0, "Round Brackets not opened, too many )'s")
                End If
            End If

            If nCloseSquig <> nOpenSquig Then
                If nCloseSquig < nOpenSquig Then
                    Throw New StreamCalculatorObjectParseException(EvalString, 0, "Curly Brackets not closed, not enough }'s")
                Else
                    Throw New StreamCalculatorObjectParseException(EvalString, 0, "Curly Brackets not opened, too many}'s")
                End If
            End If

            ' Save the Targets name
            mTargetName = EvalString.Substring(0, nIndex).Trim

            Dim strEval As String = EvalString.Substring(nIndex + 1)

            Dim rgxMatch As New System.Text.RegularExpressions.Regex("^(\[.*\]|\{.*\}|\-?[0-9]+\.?[0-9]*|[\-\+\*\^/\ \)\(])*$")
            Dim rgxMatches As System.Text.RegularExpressions.MatchCollection

            If Not rgxMatch.IsMatch(strEval) Then
                Throw New StreamCalculatorObjectParseException(strEval, 0, "Invalid Characters")
            End If

            ' Check if value/variable has not operator next to bracket
            rgxMatch = New System.Text.RegularExpressions.Regex("((\[.*\]|\{.*\}|\-?[0-9]+\.?[0-9]*)\ *\(|\)\ *(\[.*\]|\{.*\}|\-?[0-9]+\.?[0-9]*))") '"([A-Z0-9\.]\(|\)[A-Z0-9\.])")

            If rgxMatch.IsMatch(strEval) Then
                rgxMatches = rgxMatch.Matches(strEval)
                Throw New StreamCalculatorObjectParseException(strEval, rgxMatches(0).Index, "Bracket and Value without operator")
            End If

            ' Check if operator is sitting in the middle of nowhere
            rgxMatch = New System.Text.RegularExpressions.Regex("([\-\+\*\^/]\ *\)|\(\ *((?!\-[0-9])\-|[\+\*\^/])|^\ *((?!\-[0-9])\-|[\+\*\^/])|[\-\+\*\^/]\ *$)")
            If rgxMatch.IsMatch(strEval) Then
                rgxMatches = rgxMatch.Matches(strEval)
                Throw New StreamCalculatorObjectParseException(strEval, rgxMatches(0).Index, "Bracket and operator without value")
            End If

            rgxMatch = New System.Text.RegularExpressions.Regex("[\-\+\*\^/]\ *((?!\-[0-9])\-|[\+\*\^/])")
            If rgxMatch.IsMatch(strEval) Then
                rgxMatches = rgxMatch.Matches(strEval)
                Throw New StreamCalculatorObjectParseException(strEval, rgxMatches(0).Index, "Operators in succession without values")
            End If

            rgxMatch = New System.Text.RegularExpressions.Regex("(\[.*\]|\{.*\}|\-?[0-9]+\.?[0-9]*)\ +(\[.*\]|\{.*\}|\-?[0-9]+\.?[0-9]*)")
            If rgxMatch.IsMatch(strEval) Then
                rgxMatches = rgxMatch.Matches(strEval)
                Throw New StreamCalculatorObjectParseException(strEval, rgxMatches(0).Index, "Values without Operators")
            End If

            Dim nPos As UInt32 = 0
            Dim nNum As Integer = -1

            ' Create the Calculator instance
            mCalculator = New Calculator("(" & strEval & ")", nPos, Nothing, 0, 100)

            mCalculator.Clean()

            mCalculator.Assign(nNum)
        End Sub



        ''' <summary>
        ''' Evaluates the current object and returns a value
        ''' </summary>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Evaluate(ByVal InStream As DataStream.Stream) As Double
            Debug.Print(String.Format("Value from eval is : {0}", mCalculator.Calculate(InStream)))
        End Function

        ''' <summary>
        ''' Calculates the current object and saves the value
        ''' back into the stream
        ''' </summary>
        ''' <param name="InStream"></param>
        ''' <returns></returns>
        ''' <remarks></remarks>
        Public Function Calculate(ByRef InStream As DataStream.Stream) As Boolean
            If mCalculator Is Nothing Then
                Return False
            Else
                Dim value As Single = mCalculator.Calculate(InStream)
                If InStream.contains(Me.mTargetName) Then
                    InStream.Item(Me.mTargetName).Value = value
                Else
                    InStream.Add(New StreamItem(mTargetName, StreamItemType.UserValue, value))
                End If

                Return True
            End If
        End Function

        Public Sub Draw(ByRef pt As System.Drawing.Point, ByRef e As System.Drawing.Graphics)
            Me.mCalculator.Draw(pt, e)
        End Sub
#End Region

#Region "Actual Calculations"
        Private Class Calculator
            Private Enum alu_operation As Byte
                MULTIPLY = 0
                ADDITION = 1
                POWEROF = 2
                DIVIDE = 3
                SUBTRACTION = 4
            End Enum
            Private Enum alu_valueType As Byte
                FLOAT = 0
                VARIABLE_LOOKUP = 1
                ALU_REFERENCE = 2
                CUSTOM_FUNCTION = 3
            End Enum

            Private mParentNode As Calculator = Nothing
            Private mBracketNode As Calculator = Nothing
            Private mOperationNode As Calculator = Nothing

            Private mOperation As alu_operation = alu_operation.ADDITION
            Private mValue As Single = 0
            Private mValueParse As Single = 0
            Private mValueName As String
            Private mVariable As alu_valueType = alu_valueType.FLOAT

            Private mCount As Byte = 0
            Private mAluNum As Integer = 0

            Public Sub New(ByRef Bracket As Calculator, ByRef Parent As Calculator, ByRef Operation As Calculator)
                mParentNode = Parent
                mBracketNode = Bracket
                mOperationNode = Operation
            End Sub
            Public Sub New(ByVal equation As String, ByRef position As UInt16, ByRef Parent As Calculator, ByVal Count As Byte, ByVal MaxAlus As Integer)
                If Count > MaxAlus Then
                    Throw New StreamCalculatorObjectParseException(equation, position)
                End If

                mCount = Count
                mParentNode = Parent
                Dim sngTmpMult As Single = 1
                Dim strTmpValue As String = ""

                If position = 0 Then

                    Dim strNew As String = ""
                    Dim bClrSpace As Boolean = True
                    Dim nAddCnt As Integer = 0
                    For nCnt As Integer = 0 To equation.Length - 1
                        If equation.Chars(nCnt) = "[" Then
                            bClrSpace = False
                        End If
                        If equation.Chars(nCnt) = "]" Then
                            bClrSpace = True
                        End If

                        If Not bClrSpace _
                        Or (bClrSpace And equation.Chars(nCnt) <> " ") Then
                            strNew &= equation.Chars(nCnt)
                        End If
                    Next

                    equation = strNew

                End If

start:          If position + 1 < equation.Length And equation <> "" Then

                    If isBracket() Then
                        mOperationNode = New Calculator(equation, position, Me, Count + 1, MaxAlus)
                        Return
                    End If

                    If equation.Chars(position) = ")" Then
                        position += 1
                        'GoTo start
                        Return
                    Else
                        'get the value here!
                        ' Determine if its a - + * / or ^
                        Select Case equation.Chars(position)
                            Case "*"
                                mOperation = alu_operation.MULTIPLY
                                sngTmpMult = 1
                                position += 1
                            Case "/"
                                mOperation = alu_operation.DIVIDE
                                sngTmpMult = 1
                                position += 1
                            Case "-"
                                If Not (equation.Chars(position + 1) < "0" _
                                And equation.Chars(position + 1) > "9") Then
                                    mOperation = alu_operation.SUBTRACTION
                                    sngTmpMult = -1
                                    position += 1
                                End If
                            Case "+"
                                mOperation = alu_operation.ADDITION
                                sngTmpMult = 1
                                position += 1
                            Case "^"
                                mOperation = alu_operation.POWEROF
                                sngTmpMult = 1
                                position += 1
                        End Select

                        If equation.Chars(position) = "(" Then
                            position += 1
                            mBracketNode = New Calculator(equation, position, Me, 0, MaxAlus)
                            If position + 2 < equation.Length AndAlso equation.Chars(position + 2) <> ")" Then
                                mOperationNode = New Calculator(equation, position, Me, Count + 1, MaxAlus)
                            End If
                        End If

                        strTmpValue = ""
readStr:                If equation.Length > position AndAlso _
                            (equation.Chars(position) <> "*" _
                            And (equation.Chars(position) <> "-" _
                                Or (equation.Length > position + 1 _
                                    AndAlso equation.Chars(position) = "-" _
                                    AndAlso equation.Chars(position + 1) >= "0" _
                                    AndAlso equation.Chars(position + 1) <= "9")) _
                            And equation.Chars(position) <> "+" _
                            And equation.Chars(position) <> "^" _
                            And equation.Chars(position) <> "/" _
                            And equation.Chars(position) <> ")" _
                            And equation.Chars(position) <> "(") _
                        Then
                            strTmpValue &= equation.Chars(position)
                            position += 1
                            GoTo readStr
                        End If
                        If strTmpValue <> "" Then
                            If Regex.IsMatch(strTmpValue, "^\[.*\]") Then
                                ' Checking for a Square Brackets enclosed variable
                                Me.mValueName = strTmpValue.Substring(1, strTmpValue.Length - 2)
                                Me.mVariable = alu_valueType.VARIABLE_LOOKUP

                            ElseIf Regex.IsMatch(strTmpValue, "(^\-?[0-9]+\.?[0-9]*)") Then
                                ' Checking for a Floating Point variable
                                Me.mVariable = alu_valueType.FLOAT
                                mValueParse = Val(strTmpValue)

                            End If
                        End If

                        If position < equation.Length AndAlso _
                            (equation.Chars(position) = "*" _
                            Or (equation.Chars(position) = "-" _
                                And Not (equation.Length > position + 1 _
                                   AndAlso equation.Chars(position + 1) >= "0" _
                                    AndAlso equation.Chars(position + 1) <= "9")) _
                            Or equation.Chars(position) = "+" _
                            Or equation.Chars(position) = "/" _
                            Or equation.Chars(position) = "^") _
                        Then
                            mOperationNode = New Calculator(equation, position, Me, Count + 1, MaxAlus)
                            ' Return
                        End If

                        'GoTo start
                    End If
                End If
            End Sub

            Public Sub Clean()
                If isBracket Then
                    mBracketNode.Clean()
                    If Not hasOperation And Not IsNothing(mParentNode) AndAlso Not mParentNode.isBracket Then
                        ' Delete Self!
                        mBracketNode.mParentNode = Me.mParentNode
                        If Me.mParentNode.mBracketNode Is Me Then
                            mParentNode.mBracketNode = mBracketNode
                        Else
                            mParentNode.mOperationNode = mBracketNode
                        End If
                    End If
                End If
                If mCount >= 2 Then
                    Dim tmpNode As New Calculator(mParentNode.mParentNode, mParentNode.mParentNode.mParentNode, Me)
                    Dim tmpUnRefNode As Calculator = mParentNode
                    mParentNode = tmpNode
                    tmpUnRefNode.mOperationNode = Nothing
                    tmpUnRefNode.mParentNode.mParentNode.mBracketNode = tmpNode
                    tmpUnRefNode.mParentNode.mParentNode = tmpNode
                    Me.mCount = 1
                End If
                If hasOperation Then
                    mOperationNode.Clean()
                End If
            End Sub
            Public Sub Assign(ByRef ALUNum As Integer)
                mAluNum = ALUNum
                If hasOperation Then
                    mOperationNode.Assign(ALUNum)
                End If
                If isBracket Then
                    ALUNum += 1
                    mBracketNode.Assign(ALUNum)
                End If
            End Sub

            Public Function Units(Optional ByVal AU As Integer = 0) As Integer
                If isBracket Then
                    AU = Me.mBracketNode.Units(AU)
                End If
                If Me.mAluNum > AU Then
                    AU = Me.mAluNum
                End If
                If hasOperation Then
                    AU = Me.mOperationNode.Units(AU)
                End If
                Return AU
            End Function

            ''' <summary>
            ''' Is a bracket is it links to another StreamCalculatorObject via the bracket StreamCalculatorObject link
            ''' </summary>
            ''' <value></value>
            ''' <returns></returns>
            ''' <remarks></remarks>
            Private ReadOnly Property isBracket() As Boolean
                Get
                    If Not IsNothing(mBracketNode) Then
                        Return True
                    Else
                        Return False
                    End If
                End Get
            End Property
            Private ReadOnly Property hasOperation() As Boolean
                Get
                    If IsNothing(mOperationNode) Then
                        Return False
                    Else
                        Return True
                    End If
                End Get
            End Property

            Private Function getBracketNode() As Calculator
                Return mBracketNode
            End Function
            Private Function getOperaterionNode() As Calculator
                Return mOperationNode
            End Function

            Private Property operation() As alu_operation
                Get
                    Return mOperation
                End Get
                Set(ByVal value As alu_operation)
                    mOperation = value
                End Set
            End Property
            Private Property value() As Single
                Get
                    Return mValue
                End Get
                Set(ByVal value As Single)
                    mValue = value
                End Set
            End Property
            Private Property variables() As alu_valueType
                Get
                    Return mVariable
                End Get
                Set(ByVal value As alu_valueType)
                    mVariable = value
                End Set
            End Property

            Public Sub Print(ByRef Indent As String)
                '' Prints to console the variables!
                Console.WriteLine(Indent & "Value: " & mValue & " Operation: " & System.Enum.GetName(GetType(alu_operation), mOperation) & " Variable: " & mVariable & " Count: " & mCount)
                If Not IsNothing(mOperationNode) Then
                    Console.WriteLine(Indent & "+++Has Operation")
                    mOperationNode.Print(Indent)
                End If
                If Not IsNothing(mBracketNode) Then
                    Console.WriteLine(Indent & "---Has Brackets")
                    Indent &= Chr(9)
                    mBracketNode.Print(Indent)
                    Indent = Indent.Substring(0, Indent.Length - 1)
                End If
            End Sub
            Public Sub Draw(ByRef pt As System.Drawing.Point, ByRef e As System.Drawing.Graphics)
                Dim sngWidth As Single = 0
                Dim sz As New System.Drawing.Size(30, 30)
                Dim strText As String = ""
                Dim fntText As System.Drawing.Font = New System.Drawing.Font("Courier New", 14, System.Drawing.GraphicsUnit.Pixel)
                Dim fillColour As System.Drawing.Brush = Drawing.Brushes.Black

                If hasOperation Then
                    If mOperationNode.isBracket Then
                        If isBracket Then sngWidth = mBracketNode.DrawWidth
                        sngWidth = 4 + (2 * sngWidth)

                        e.DrawLine(Drawing.Pens.Black, CSng(pt.X), CSng(pt.Y + (sz.Height / 2)), CSng(pt.X + sz.Width + (sz.Width * sngWidth)), CSng(pt.Y + (sz.Height / 2)))

                        pt.X += sz.Width * sngWidth
                        mOperationNode.Draw(pt, e)
                        pt.X -= sz.Width * sngWidth
                    Else
                        e.DrawLine(Drawing.Pens.Black, CSng(pt.X), CSng(pt.Y + (sz.Height / 2)), CSng(pt.X + sz.Width + (sz.Width * 2)), CSng(pt.Y + (sz.Height / 2)))
                        pt.X += sz.Width * 2
                        mOperationNode.Draw(pt, e)
                        pt.X -= sz.Width * 2
                    End If
                End If

                If isBracket Then
                    fillColour = System.Drawing.Brushes.Blue
                    e.DrawLine(Drawing.Pens.Black, CSng(pt.X + (sz.Width / 2)), CSng(pt.Y), CSng(pt.X + (sz.Width / 2)), CSng(pt.Y + sz.Height + (sz.Height * 2)))
                    pt.Y += sz.Height * 2
                    mBracketNode.Draw(pt, e)
                    pt.Y -= sz.Height * 2
                End If

                e.FillRectangle(fillColour, New System.Drawing.RectangleF(pt, sz))

                Select Case Me.operation
                    Case alu_operation.ADDITION
                        strText = "+"
                    Case alu_operation.DIVIDE
                        strText = "/"
                    Case alu_operation.MULTIPLY
                        strText = "*"
                    Case alu_operation.POWEROF
                        strText = "^"
                    Case alu_operation.SUBTRACTION
                        strText = "-"
                End Select

                If Me.mVariable = alu_valueType.FLOAT Then
                    strText &= Me.mValue
                ElseIf Me.mVariable = alu_valueType.VARIABLE_LOOKUP Then
                    strText &= Me.mValueName
                End If

                e.DrawString(strText, fntText, Drawing.Brushes.White, pt)

                e.DrawString("A:" & mAluNum, fntText, Drawing.Brushes.Coral, New System.Drawing.PointF(pt.X, pt.Y + 16))

            End Sub
            Private Function DrawWidth() As Single
                Dim sngValue As Single = 0
                If isBracket And hasOperation AndAlso mOperationNode.isBracket Then
                    sngValue = 2 + mOperationNode.DrawWidth
                End If
                If hasOperation AndAlso mOperationNode.hasOperation Then
                    sngValue += 1 + mOperationNode.DrawWidth
                ElseIf hasOperation Then
                    sngValue += mOperationNode.DrawWidth
                End If
                If isBracket Then
                    sngValue += mBracketNode.DrawWidth
                End If
                Return sngValue
            End Function

            Private Function CalculateOperation(ByVal value As Single) As Single
                Console.WriteLine("Value: " & mValue & " Operation: " & System.Enum.GetName(GetType(alu_operation), mOperation) & " Variable: " & mVariable)
                Dim sngValue As Single = 0
                Select Case mOperation
                    Case alu_operation.ADDITION
                        sngValue = value + mValue
                    Case alu_operation.DIVIDE
                        sngValue = value / mValue
                    Case alu_operation.MULTIPLY
                        sngValue = value * mValue
                    Case alu_operation.SUBTRACTION
                        sngValue = value - mValue
                End Select
                Return sngValue
            End Function
            Public Function Calculate(ByRef InStream As DataStream.Stream, Optional ByVal value As Single = 0) As Single
                'Console.WriteLine("CALC: Value: " & mValue & " Operation: " & System.Enum.GetName(GetType(alu_operation), mOperation) & " Variable: " & mVariable)
                Dim sngValue As Single = 0
                Dim bCalcBypass As Boolean = False
                ' calculate all brackets first

                If Me.mVariable = alu_valueType.VARIABLE_LOOKUP Then
                    If InStream.contains(Me.mValueName) Then
                        mValue = InStream(Me.mValueName).Value
                    Else
                        mValue = 0
                    End If
                Else
                    mValue = mValueParse
                End If

                If isBracket Then
                    mValue = mBracketNode.Calculate(InStream)
                    'Console.WriteLine("Return from bracket: " & mValue)
                End If

                If hasOperation Then
                    sngValue = mOperationNode.Calculate(InStream)
                    'Console.WriteLine("Return from operation: " & sngValue)
                    bCalcBypass = True
                End If
                If isBracket And hasOperation AndAlso mOperationNode.isBracket Then
                    'Console.WriteLine("END CALCULATION")
                    Select Case mOperationNode.operation
                        Case alu_operation.ADDITION
                            sngValue = mValue + sngValue
                        Case alu_operation.DIVIDE
                            If sngValue <> 0 Then
                                sngValue = mValue / sngValue
                            Else
                                sngValue = 0
                            End If
                        Case alu_operation.MULTIPLY
                            sngValue = mValue * sngValue
                        Case alu_operation.POWEROF
                            sngValue = mValue ^ sngValue
                        Case alu_operation.SUBTRACTION
                            sngValue = mValue - sngValue
                    End Select
                End If

                If isBracket And Not IsNothing(mParentNode) AndAlso Not mParentNode.isBracket Then
                Else
                    If isBracket And Not hasOperation Then
                        Return mValue
                    End If
                End If

                If (Not isBracket Or (isBracket And hasOperation AndAlso mOperationNode.isBracket)) And Not bCalcBypass _
                Or (isBracket And Not IsNothing(mParentNode) AndAlso Not mParentNode.isBracket) Then
                    Select Case mOperation
                        Case alu_operation.ADDITION
                            sngValue = mParentNode.value + mValue
                        Case alu_operation.DIVIDE
                            If mValue <> 0 Then
                                sngValue = mParentNode.value / mValue
                            Else
                                sngValue = 0
                            End If
                        Case alu_operation.MULTIPLY
                            sngValue = mParentNode.value * mValue
                        Case alu_operation.POWEROF
                            sngValue = mParentNode.value ^ mValue
                        Case alu_operation.SUBTRACTION
                            sngValue = mParentNode.value - mValue
                    End Select
                End If

                Return sngValue
            End Function
        End Class
#End Region
    End Class
End Namespace
